package com.isoft.service;

import com.isoft.config.AppWatchConfig;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

@Service
public class EnhancedXmlFileWatcherService {
    private static final Logger logger = LoggerFactory.getLogger(EnhancedXmlFileWatcherService.class);

    private final AppWatchConfig config;
    private final List<XmlFileHandler> handlers;
    private final ExecutorService watchExecutor;
    private final ExecutorService processExecutor;
    private final Map<Path, Long> lastEventTime = new ConcurrentHashMap<>();
    private final Set<Path> processingFiles = ConcurrentHashMap.newKeySet();
    private final BlockingQueue<WatchEventContext> fileQueue = new LinkedBlockingQueue<>(100);
    private final AtomicBoolean running = new AtomicBoolean(true);
    private WatchService watchService;

    private static class WatchEventContext {
        final Path filePath;
        final WatchEvent.Kind<?> eventKind;
        final long timestamp;

        WatchEventContext(Path filePath, WatchEvent.Kind<?> eventKind) {
            this.filePath = filePath;
            this.eventKind = eventKind;
            this.timestamp = System.currentTimeMillis();
        }
    }

    public EnhancedXmlFileWatcherService(AppWatchConfig config,
                                         List<XmlFileHandler> handlers) {
        this.config = config;
        this.handlers = handlers;
        this.watchExecutor = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r);
            t.setName("文件监听线程");
            t.setDaemon(true);
            return t;
        });
        this.processExecutor = new ThreadPoolExecutor(
                config.getMaxThreads(),
                config.getMaxThreads(),
                30, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000),
                r -> {
                    Thread t = new Thread(r);
                    t.setName("文件处理线程");
                    t.setDaemon(true);
                    return t;
                },
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }

    @PostConstruct
    public void init() {
        startWatching();
        startProcessingWorkers();
        logger.info("XML文件监听服务已初始化，使用 {} 个工作线程", config.getMaxThreads());
        logger.info("监听目录: {}", config.getDirectory());
        logger.info("成功文件目录: {}/backup/success", config.getDirectory());
        logger.info("失败文件目录: {}/backup/failed", config.getDirectory());
        logger.info("文件匹配规则: {}", config.getFilePattern());
        logger.info("文件稳定性检查次数: {}，检查间隔: {}ms", config.getStabilityChecks(), config.getCheckIntervalMs());
        logger.info("事件冷却时间: {}ms，最大队列长度: {}，最大线程数: {}", config.getEventCooldownMs(), fileQueue.remainingCapacity(), config.getMaxThreads());
        logger.info("说明：\n1. 所有新XML文件会被自动检测并处理。\n2. 处理成功的文件会被移动到 success 文件夹，失败的文件会被移动到 failed 文件夹。\n3. 监听和处理过程有多线程并发，确保高效。\n4. 文件必须稳定（大小和修改时间不变）两次后才会被处理。\n5. 处理失败可在 failed 文件夹中查看原始文件。\n6. 详细日志会输出到 logs/xml-parse.log。\n");
    }

    @PreDestroy
    public void cleanup() {
        stopWatching();
    }

    private void startWatching() {
        watchExecutor.submit(() -> {
            try {
                initializeWatchService();
                processWatchEvents();
            } catch (Exception e) {
                logger.error("文件监听服务发生错误", e);
                if (running.get()) {
                    logger.info("正在尝试重新启动文件监听服务...");
                    startWatching();
                }
            }
        });
    }

    private void initializeWatchService() throws IOException {
        watchService = FileSystems.getDefault().newWatchService();
        Path path = Paths.get(config.getDirectory());

        if (!Files.exists(path)) {
            Files.createDirectories(path);
            logger.info("已创建监听目录: {}", path);
        }

        path.register(watchService,
                StandardWatchEventKinds.ENTRY_CREATE,
                StandardWatchEventKinds.ENTRY_MODIFY);

        logger.info("开始监听目录: {} (文件匹配模式: {})", path, config.getFilePattern());
    }

    private void processWatchEvents() throws InterruptedException {
        while (running.get()) {
            WatchKey key;
            try {
                key = watchService.take();
            } catch (ClosedWatchServiceException e) {
                logger.warn("文件监听服务已关闭");
                break;
            }

            for (WatchEvent<?> event : key.pollEvents()) {
                handleWatchEvent(event, (Path) key.watchable());
            }

            if (!key.reset()) {
                logger.warn("监听键已失效");
                break;
            }
        }
    }

    private void handleWatchEvent(WatchEvent<?> event, Path dir) {
        if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
            logger.warn("文件系统事件溢出");
            return;
        }

        @SuppressWarnings("unchecked")
        Path filename = ((WatchEvent<Path>) event).context();
        Path filePath = dir.resolve(filename);

        if (!filename.toString().matches(config.getFilePattern()) ||
                filename.toString().startsWith(".")) {
            return;
        }

        if (processingFiles.contains(filePath)) {
            logger.debug("跳过正在处理的文件事件: {} ({})",
                    filePath, event.kind());
            return;
        }

        long now = System.currentTimeMillis();
        Long lastEvent = lastEventTime.get(filePath);

        if (lastEvent != null && (now - lastEvent) < config.getEventCooldownMs()) {
            logger.debug("跳过最近已处理的文件: {} (上次处理 {} 毫秒前)",
                    filePath, now - lastEvent);
            return;
        }

        lastEventTime.put(filePath, now);
        WatchEventContext context = new WatchEventContext(filePath, event.kind());

        if (!fileQueue.offer(context)) {
            logger.warn("文件队列已满，丢弃事件: {}", filePath);
        } else {
            logger.debug("已加入队列: {} (事件类型: {})", filePath, event.kind());
        }
    }

    private void startProcessingWorkers() {
        for (int i = 0; i < config.getMaxThreads(); i++) {
            processExecutor.submit(() -> {
                while (running.get()) {
                    try {
                        WatchEventContext context = fileQueue.poll(1, TimeUnit.SECONDS);
                        if (context != null) {
                            processFile(context);
                        }
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    } catch (Exception e) {
                        logger.error("处理文件时发生错误", e);
                    }
                }
            });
        }
    }

    private void processFile(WatchEventContext context) {
        Path filePath = context.filePath;

        if (!acquireFileLock(filePath)) {
            logger.debug("文件 {} 正在被其他线程处理", filePath);
            return;
        }

        try {
            if (!isFileReadyForProcessing(filePath)) {
                try {
                    if (Files.exists(filePath) && Files.size(filePath) == 0) {
                        logger.info("文件 {} 仍为0字节，跳过本次处理，等待下次事件", filePath);
                        return; // 不移动，等待下次事件
                    }
                } catch (Exception ignore) {}
                logger.warn("文件 {} 尚未准备好处理", filePath);
                return;
            }

            Path backupFile = createBackup(filePath);
            Optional<XmlFileHandler> handler = findHandler(filePath);

            if (handler.isPresent()) {
                processWithHandler(filePath, backupFile, handler.get());
            } else {
                moveToUnprocessed(filePath);
                logger.warn("未找到适合处理文件 {} 的处理器", filePath);
            }
        } catch (Exception e) {
            logger.error("处理文件 {} 失败", filePath, e);
            moveToFailed(filePath);
        } finally {
            releaseFileLock(filePath);
        }
    }

    private boolean isFileReadyForProcessing(Path file) {
        int maxAttempts = Math.max(10, config.getStabilityChecks() * 2);
        long startTime = System.currentTimeMillis();
        long lastSize = -1;
        long lastModified = -1;
        int stableCount = 0;
        for (int attempt = 1; attempt <= maxAttempts && running.get(); attempt++) {
            try {
                if (!Files.exists(file) || !Files.isReadable(file) || Files.size(file) == 0) {
                    logger.debug("文件访问检查失败 [尝试 {}/{}]: {} (不存在/不可读/空)",
                            attempt, maxAttempts, file);
                    Thread.sleep(config.getCheckIntervalMs());
                    stableCount = 0;
                    continue;
                }
                try (java.io.FileInputStream fis = new java.io.FileInputStream(file.toFile())) {
                    // 能打开就说明没被独占
                } catch (Exception e) {
                    logger.debug("文件只读打开失败 [尝试 {}/{}]: {}，异常: {}",
                            attempt, maxAttempts, file, e.toString());
                    Thread.sleep(config.getCheckIntervalMs());
                    stableCount = 0;
                    continue;
                }
                long currentSize = Files.size(file);
                long currentModified = Files.getLastModifiedTime(file).toMillis();
                if (currentSize == lastSize && currentModified == lastModified) {
                    stableCount++;
                    if (stableCount >= 2) {
                        return true;
                    }
                } else {
                    stableCount = 1;
                }
                lastSize = currentSize;
                lastModified = currentModified;
                Thread.sleep(config.getCheckIntervalMs());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            } catch (Exception e) {
                logger.debug("文件检查错误 [尝试 {}/{}]: {}，异常: {}",
                        attempt, maxAttempts, file, e.toString());
                stableCount = 0;
            }
        }
        return false;
    }

    private Optional<XmlFileHandler> findHandler(Path filePath) {
        return handlers.stream()
                .filter(h -> h.supports(filePath))
                .findFirst();
    }

    private void processWithHandler(Path originalFile, Path backupFile, XmlFileHandler handler) {
        try {
            long startTime = System.currentTimeMillis();
            String result = handler.handle(backupFile);
            long duration = System.currentTimeMillis() - startTime;

            logger.info("已处理文件 {}，耗时 {} 毫秒。处理结果: {}",
                    originalFile.getFileName(), duration, result);

            moveToSuccess(originalFile);
        } catch (Exception e) {
            moveToFailed(originalFile);
            throw new RuntimeException("处理器处理文件失败", e);
        }
    }

    private Path createBackup(Path source) throws IOException {
        Path backupDir = Paths.get(config.getDirectory(), "backup");
        Files.createDirectories(backupDir);
        return Files.copy(source, backupDir.resolve(source.getFileName()),
                StandardCopyOption.REPLACE_EXISTING);
    }

    private void moveToSuccess(Path file) throws IOException {
        moveFile(file, "success");
    }

    private void moveToUnprocessed(Path file) {
        try {
            moveFile(file, "unprocessed");
        } catch (IOException e) {
            logger.error("移动文件到未处理目录失败: {}", file, e);
        }
    }

    private void moveToFailed(Path file) {
        try {
            moveFile(file, "failed");
        } catch (IOException e) {
            logger.error("移动文件到失败目录失败: {}", file, e);
        }
    }

    private void moveFile(Path source, String subdir) throws IOException {
        Path targetDir = Paths.get(config.getDirectory(), "backup", subdir);
        Files.createDirectories(targetDir);

        Path target = targetDir.resolve(source.getFileName());
        try {
            Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
        } catch (AtomicMoveNotSupportedException e) {
            Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    private boolean acquireFileLock(Path file) {
        return processingFiles.add(file.toAbsolutePath());
    }

    private void releaseFileLock(Path file) {
        processingFiles.remove(file.toAbsolutePath());
    }

    private void stopWatching() {
        running.set(false);
        watchExecutor.shutdown();
        processExecutor.shutdown();

        try {
            if (!watchExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
                watchExecutor.shutdownNow();
            }
            if (!processExecutor.awaitTermination(10, TimeUnit.SECONDS)) {
                processExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        logger.info("文件监听服务已停止");
    }
}